/*
 * ==========================================================================*\
 * | $Id$
 * |*-------------------------------------------------------------------------*|
 * | Copyright (C) 2009 Virginia Tech | | This file is part of CloudSpace. | |
 * CloudSpace is free software; you can redistribute it and/or modify | it under
 * the terms of the GNU General Public License as published | by the Free
 * Software Foundation; either version 3 of the License, or | (at your option)
 * any later version. | | CloudSpace is distributed in the hope that it will be
 * useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of |
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General
 * Public License for more details. | | You should have received a copy of the
 * GNU General Public License | along with CloudSpace; if not, see
 * <http://www.gnu.org/licenses/>.
 * \*==========================================================================
 */

package cloudspace.demoitem;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import cloudspace.config.ServletLocationFinder;

/**
 * The model representation for the demo items.
 * It provides the abstraction of writing to the actual XML file and
 * reading the XML file. Anyone outside is only aware of the existance
 * of a bean representation of the XML.
 * 
 * TODO: We can probably use a better mechanism to parse the file. Also
 * the number of reads that we do on the file seems large.
 * 
 * @author Sunil Kamalakar
 *
 */
public class DemoItemsModel
{
	//The location of the file in the server.
	private static String DEMO_PAGE_XML_LOCATION = 
			ServletLocationFinder.getServletResourcePath("/") + "/data/DemoPageData.xml";
		
	//The actual file object on which we synchronize for read and writes.
	private static File demoXmlFile;
	
	//The XML tags used.
	private static final String DEMO_XML_DEMO_ITEMS = "DemoItems";
	private static final String DEMO_XML_DEMO_ITEM = "DemoItem";
	private static final String DEMO_XML_ATTR_VISIBLE = "visible";
	private static final String DEMO_XML_PROGRAMURI = "ProgramURI";
	private static final String DEMO_XML_IMAGEURI = "ImageURI";
	private static final String DEMO_XML_TOOLTIP = "Tooltip";
	
	//The logger
	private static Logger log = Logger.getLogger(DemoItemsModel.class);

	
	//The constructor
	public DemoItemsModel() 
				throws ParserConfigurationException, SAXException, 
						IOException, TransformerException {
		
		//Create the demo file object.
		demoXmlFile = new File(DEMO_PAGE_XML_LOCATION);
		
		//If the file does not exist, then create an empty one.
		if(demoXmlFile == null || !demoXmlFile.exists()) {
			createDefaultDemoFile();
		}
	}
	
	private void createDefaultDemoFile() throws ParserConfigurationException, 
							SAXException, IOException, TransformerException{

		String defaultXmlContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
				"<" + DEMO_XML_DEMO_ITEMS + ">" +
				"</" + DEMO_XML_DEMO_ITEMS + ">";
		
		log.info("Creating the default demo file");
		
		DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		InputSource inputStream = new InputSource(new StringReader(defaultXmlContent));
		org.w3c.dom.Document doc = db.parse(inputStream);
		writeToDemoFile(doc);
		readFromDemoFile();
	}
	
	/**
	 * The reading of the file which return the entire XML document.
	 */
	private Document readFromDemoFile() throws SAXException, IOException, 
											ParserConfigurationException {
		
		DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		org.w3c.dom.Document doc = null;
		
		synchronized (demoXmlFile) {
			doc = db.parse(demoXmlFile);
		}
		
		return doc;
	}
	
	/**
	 * This method writes the XML document back to the file.
	 */
	private void writeToDemoFile(Document doc) 
						throws TransformerException { 
		
		// Prepare the DOM document for writing 
		Source source = new DOMSource(doc); 
		
		synchronized (demoXmlFile) {
			// Write the DOM document to the file 
			Result result = new StreamResult(demoXmlFile);
			
			Transformer xformer = 
						TransformerFactory.newInstance().newTransformer();
			xformer.setOutputProperty(OutputKeys.INDENT, "yes");
			xformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
			
			xformer.transform(source, result);
		}
	}
	
	/**
	 * This method reads the XML document for the demo-items and constructs the bean representation 
	 * for each of the demo-items present.
	 * @return A list of DemoItemBeans.
	 * @throws SAXException
	 * @throws IOException
	 * @throws ParserConfigurationException
	 */
	public List<DemoItemBean> readDemoFile() throws SAXException, IOException, 
													ParserConfigurationException {
		
		List<DemoItemBean> demoItems = new ArrayList<DemoItemBean>();
		Document doc = readFromDemoFile();
		
		NodeList nodeList = doc.getElementsByTagName(DEMO_XML_DEMO_ITEM);
	    for (int i = 0; i < nodeList.getLength(); i++) {
	        Node node = nodeList.item(i);
	        if (node.getNodeType() == Node.ELEMENT_NODE) {
	            String programURI = null, imageURI = null, tooltip = null;
				boolean visibility = 
						((Element)node).getAttribute(DEMO_XML_ATTR_VISIBLE).equals("true")?true:false;
	            NodeList childNodes = node.getChildNodes();
	            for( int j = 0; j < childNodes.getLength(); j++) {
					Node childNode = childNodes.item(j);
					if (childNode.getNodeType() == Node.ELEMENT_NODE) {
						
						if(childNode.getNodeName().equals(DEMO_XML_PROGRAMURI)) {
							programURI = childNode.getTextContent();
						}						
						if(childNode.getNodeName().equals(DEMO_XML_IMAGEURI)) {
							imageURI = childNode.getTextContent();
						}			
						if(childNode.getNodeName().equals(DEMO_XML_TOOLTIP)) {
							tooltip = childNode.getTextContent();
						}
						if(programURI != null && programURI != "" && imageURI != null && tooltip != null) {
							demoItems.add(new DemoItemBean(programURI, imageURI, tooltip, visibility));						
						}
					}
				} 
	        }
	    }
				
		return demoItems;
	}

	/**
	 * Add the bean that is provided to the demo items list.
	 * 
	 * @param bean
	 * @return the status of the add operation.
	 * @throws TransformerException
	 * @throws SAXException
	 * @throws IOException
	 * @throws ParserConfigurationException
	 */
	public boolean addDemoItemToFile(DemoItemBean bean)
				throws TransformerException, SAXException, 
						IOException, ParserConfigurationException, Exception {
		
		boolean retVal = false;		
	
		if(bean != null) {
			log.debug("Adding bean: " + bean.toString());
			retVal = addDemoItemToFile(bean.getProgramURI(), bean.getImageURI(), 
					bean.getTooltip(), bean.getVisibility());		
		}
		
		return retVal;
	}
	
	/**
	 * The private method to add the corresponding values of the bean to the demo items list.
	 * 
	 * @param programURIStr
	 * @param imageURIStr
	 * @param tooltipStr
	 * @param visible
	 * @return
	 * @throws TransformerException
	 * @throws SAXException
	 * @throws IOException
	 * @throws ParserConfigurationException
	 */
	private synchronized boolean addDemoItemToFile(
						String programURIStr, String imageURIStr,
						String tooltipStr, boolean visible) 
						throws TransformerException, SAXException, IOException, 
							 	ParserConfigurationException, Exception {
			
		org.w3c.dom.Document doc = readFromDemoFile();
		
		if(doesElementExists(doc, programURIStr)) {
			//Pre-mature return, since we do not want to add a new element.
			//We do not want to support duplicate entries in the list pointing to the
			//same URL. We avoid duplicates this way.
			throw new Exception("Duplicate entries in the demo list not allowed: " + programURIStr);
		}
		
		Node root = doc.getDocumentElement();
		Element demoItem = doc.createElement(DEMO_XML_DEMO_ITEM);
		demoItem.setAttribute(DEMO_XML_ATTR_VISIBLE, visible?"true":"false");
		
		Element programURI = doc.createElement(DEMO_XML_PROGRAMURI);
		programURI.setTextContent(programURIStr);
		demoItem.appendChild(programURI);

		Element imageURI = doc.createElement(DEMO_XML_IMAGEURI);
		imageURI.setTextContent(imageURIStr);
		demoItem.appendChild(imageURI);
	
		Element tooltip = doc.createElement(DEMO_XML_TOOLTIP);
		tooltip.setTextContent(tooltipStr);
		demoItem.appendChild(tooltip);

		root.appendChild(demoItem);
		doc.normalize();
		
		//Now ask for the contents to be written into the file.
		writeToDemoFile(doc);
		return true;
	}

	/**
	 * This method provides the update functionality on an existing
	 * demo item in the XML. We update the value in-place on the XML.
	 * 
	 * @param bean - Existing bean
	 * @param newBean - Updated bean
	 * @return status
	 * @throws SAXException
	 * @throws IOException
	 * @throws ParserConfigurationException
	 * @throws TransformerException
	 */
	public synchronized boolean updateDemoItemInFile(DemoItemBean bean, DemoItemBean newBean ) 
					throws SAXException, IOException, 
					ParserConfigurationException, TransformerException, Exception {
			
		String programURI = bean.getProgramURI();
		String newProgramURI = newBean.getProgramURI();
		String newImageURI = newBean.getImageURI();
		String newTooltip = newBean.getTooltip();
		boolean visibility = newBean.getVisibility();
		
		log.debug("Updating bean: " + bean + "\nwith values" + newBean);

		org.w3c.dom.Document doc = readFromDemoFile();
		
		NodeList nodeList = doc.getElementsByTagName(DEMO_XML_DEMO_ITEM);
	    for (int i = 0; i < nodeList.getLength(); i++) {
	        Node node = nodeList.item(i);
	        if (node.getNodeType() == Node.ELEMENT_NODE) {
	            NodeList childNodes = node.getChildNodes();
	            for(int j = 0; j < childNodes.getLength(); j++) {
					Node childNode = childNodes.item(j);
					if (childNode.getNodeType() == Node.ELEMENT_NODE) {
						if(childNode.getTextContent().equals(programURI)) {
							
							((Element)node).setAttribute(DEMO_XML_ATTR_VISIBLE, visibility?"true":"false");
							
							//If the updated URI does not collide with an existing URI, then add it.
							if(!programURI.equals(newProgramURI) && 
													doesElementExists(doc, newProgramURI)) {
								
								throw new Exception("Duplicate entries in the demo list not allowed: " + newProgramURI);
							}
							else {
								updateElementsInNode(doc, childNodes, newProgramURI, newImageURI, newTooltip);													
							}
						
					        //Now ask for the contents to be written into the file.
					    	writeToDemoFile(doc);
					        return true;
						}
					}
				}
	        }
	    }
	    
		return false;
	}

	private void updateElementsInNode(Document doc, NodeList childNodes, 
							String newProgramURI, String newImageURI, String newTooltip) {
		
		for(int k = 0; k < childNodes.getLength(); k++) {
			Node mNode = childNodes.item(k);
			if (mNode.getNodeType() == Node.ELEMENT_NODE) {
				if(mNode.getNodeName().equals(DEMO_XML_PROGRAMURI)) {
					mNode.setTextContent(newProgramURI);
				}						
				if(mNode.getNodeName().equals(DEMO_XML_IMAGEURI)) {
					mNode.setTextContent(newImageURI);
				}			
				if(mNode.getNodeName().equals(DEMO_XML_TOOLTIP)) {
					mNode.setTextContent(newTooltip);
				}
			}	
		}
	}
	
	/**
	 * Utility method to check if the a given program URI exists in the demo items list,
	 * since the ProgramURI is the unique value for each demo-item.
	 * 
	 * @param doc
	 * @param programURIStr
	 * @return status
	 */
	private boolean doesElementExists(Document doc, String programURIStr) {
		
		NodeList nodeList = doc.getElementsByTagName(DEMO_XML_DEMO_ITEM);
	    for (int i = 0; i < nodeList.getLength(); i++) {
	        Node node = nodeList.item(i);
	        if (node.getNodeType() == Node.ELEMENT_NODE) {
	            NodeList childNodes = node.getChildNodes();
	            for( int j = 0; j < childNodes.getLength(); j++) {
					Node childNode = childNodes.item(j);
					if (childNode.getNodeType() == Node.ELEMENT_NODE) {
						if(childNode.getTextContent().equals(programURIStr)) {
							return true;
						}						
					}
				} 
	        }
	    }
		
		return false;
	}
	
	/**
	 * Removes an existing bean value from the XML representation of demo-items.
	 * @param bean
	 * @return status
	 * @throws SAXException
	 * @throws IOException
	 * @throws ParserConfigurationException
	 * @throws TransformerException
	 */
	public synchronized boolean removeDemoItemFromFile(DemoItemBean bean) 
								throws SAXException, IOException, ParserConfigurationException, TransformerException {
		
		String programURIStr = bean.getProgramURI();

		boolean toRemove = false;
		org.w3c.dom.Document doc = readFromDemoFile();
		log.debug("Removing demo item: " + bean);
		
		NodeList nodeList = doc.getElementsByTagName(DEMO_XML_DEMO_ITEM);
	    for (int i = 0; i < nodeList.getLength(); i++) {
	        Node node = nodeList.item(i);
	        if (node.getNodeType() == Node.ELEMENT_NODE) {
	            NodeList childNodes = node.getChildNodes();
	            for(int j = 0; j < childNodes.getLength(); j++) {
					Node childNode = childNodes.item(j);
					if (childNode.getNodeType() == Node.ELEMENT_NODE) {
						if(childNode.getTextContent().equals(programURIStr)) {
							toRemove = true;
						}
					}
				}
	            if(toRemove) {
		        	doc.getDocumentElement().removeChild(node);
		        	
		        	//Now ask for the contents to be written into the file.
		    		writeToDemoFile(doc);
		        	return true;
		        }
	        }
	    }
	    
		return false;
	}	
}
